Release v0.15.0 - xchain-node - #32
Merged
Merged
Conversation
… ruled past instant
…d fail closed instead of skipping the chain wipe
…coder maintenance window
Re-running init no longer mints a HUB_API_KEY that was not asked for; --mint-hub-api-key is the explicit opt-in.
…e CLI pushes config to the hub `validator init` writes HUB_API_KEY to config/hub.local and the hub container deploys keyed from that sidecar, but the CLI only sent the key it found in .env. On a validator host provisioned per the runbook the config push that follows `install xchain-hub` was therefore keyless against a keyed hub and failed with HTTP 401, and so did every state-changing command after it. preCheck now fills the CLI's own env from the same sidecar before the hub is installed or pushed to. A host-env key still wins and nothing is ever minted, so a host with no sidecar stays keyless as before.
The explorer's request budgets and its tip-age freshness gate both default to values chosen for a public deployment, and a shared service has no per-venue config file to override them in. Pass EXPLORER_*RATE_LIMIT_RPM and EXPLORER_TIP_MAX_AGE_S (including the per-coin form) through from host env, the same injection point the published ports and the CORS origin already use. Both defaults misfire on a private venue. One tunnelled dev box is a single IP, so every browser and every test run shares one 500/min bucket. And a regtest chain only advances when someone mines it, so a chain nobody is driving crosses the six-hour age gate and the explorer starts refusing every read for it with COIN_DATA_STALE while its lag is zero.
A reset rebuilds a store on a new lineage, so every bootstrap already published for that combo describes the old one and a fresh install that restores it halts. A reset now records the combos it wiped, bootstrap create clears them, and the new bootstrap-republish-due command lists the difference so the publisher can pull due combos into its plan even when the schedule or the tracker opt-in would have skipped them.
An explicit null says there is no validator, so it must not fall through to the real validator directory the way an absent injection does.
Litecoin and Dogecoin regtest nodes keep a separate blockmintxfee, so a low-fee transaction is accepted into the mempool and never mined unless both floors come down together.
An epoch close needs a reachable DOGE indexer on every network, and the regtest arming opt-in has to reach the indexer and the container hub together or the venue reports a rules mismatch. The arming variable is gated on regtest here as well as in the activation module itself.
The time budgeted between a window closing and its batch being readable on chain varies by venue, so it belongs beside the other batch knobs rather than clamped to the fleet default.
The filterCommandParameters() block opened one describe too early, which listed the hub credential-report cases under the wrong suite.
…st response knobs through Regtest stacks now get the same hub-DB mirror pointer mainnet and testnet always had, including the password reconciliation that a second gate had kept off, and default the mirror grace windows to zero unless the host sets them. The hub receives the attest response forward-margin and batch window overrides when the host provides them.
…ease tree Pin Bitcoin Core 31.1 and Litecoin Core 0.21.5.6, and fix the two ways `update node` damaged a live daemon on the way there. The update path force-removed the node container before rebuilding. That is SIGKILL, so the daemon came back at its last flushed block index: a regtest rehearsal lost 16 mined blocks, and a mainnet node would face a long replay instead. The running node is now left alone until buildCryptoNode stops it with a 600 second flush budget, immediately before the removal and re-run it already performs. Containers are created with the same stop timeout so an operator's own restart is safe too. The release downloader extracted an archive into the previous version's tree. The archive's top-level directory is flattened only when it is the sole entry, so a second release landed nested beside the old bin/ and share/, and the image was built from the OLD binaries under a NEW version file. Downloads now extract into a staging sibling that is swapped in once complete, which also leaves the previous tree intact when a download or hash check fails. Bitcoin Core 31 no longer parses limitdescendantsize, replaced by the cluster limits, so drop it from the regtest config. The 28.1 and 0.21.4 hashes stay: a pinned install of either still verifies.
One wave of the review round on the xchain-platform board. Every change was re-derived from the code rather than applied from the finding recommended option, and each carries a control that reproduces the original failure. Review findings: 6411 6533 6534 6535 6536 6537
…plorer's five per-route caps through from the host env The encoder behind a cross-box reverse proxy trusts only loopback and private peers, so its per-IP limiter keyed every visitor on the proxy's egress address. ENCODER_TRUST_PROXY names that address and now survives update and recreate like the hub's settings do. The explorer's five per-route limiters were unreachable on a node-managed explorer; all eight are now host-env settable. Read by name so the env-var coverage gate sees each variable.
…ency they probe Three healthcheck startPeriod literals were each chosen per service instead of being derived from the step they actually wait on, so the encoder, hub and explorer could each be declared unhealthy while their dependency was still starting normally. A new DEPENDENCY_HEALTH_START_PERIOD in src/config/constants.js now feeds those three descriptors in ModuleService.js and MariaDB's own --health-start-period push in DatabaseService.js, so the two sides cannot drift apart in a one-sided edit. Self-judging boots (decoder /live, indexer, tracker, miner, sync's hub wait) deliberately keep their own literals. The DatabaseService assertion pins the EMITTED --health-start-period value rather than only the flag's presence, so putting a literal back on the DB side is caught. Negative controls executed: forcing the constant to 45s and to 30s reddens the encoder assertion. Review round 7 cluster 2675ab894ab8 (findings #6814, #6815, #6816) plus #6856. Also carries review round 6's node work.
The gate was on-demand only, disabled because the sub-repos were private and a scheduled run would have failed at the first clone. They are public now, so anonymous clones work and the stated reason is gone. Leaving it on-demand has a cost that a release pays. A full pass is about an hour and fifty minutes and it is the only gate that exercises consensus and money movement end to end, so a cut that meets it for the first time discovers a whole train's worth of breakage against a two-hour clock, and every failure restarts that clock. Run nightly against develop, the same discovery happens on a day when nobody is waiting on it. The coin list is event-dependent because a scheduled run carries no inputs, so without it the nightly would silently test one coin and be two thirds blind: the litecoin and dogecoin legs are where chain-specific breakage lands. A dispatch still runs exactly the single coin it names. The legs are independent stacks, so fail-fast is off: a release needs all three verdicts, not whichever failed first. Verified by dispatch on a throwaway branch: selecting litecoin produced exactly one job, e2e (litecoin), so the matrix resolves and the input is not falling back to the default.
Every leg of a three-coin matrix rendered as the identical workflow name in the run list, so the only way to tell which chain a run was grading was to open it. That is precisely the moment you cannot afford it: a release matrix is in flight, one leg has gone red, and finding it costs three clicks. The run title is a different string from the workflow name and is the one the list renders, so it carries the coin. It also carries the ref, because the same workflow grades develop, a release branch and a published tag, and during a cut the list holds runs against more than one of them at once; "which chain" stops being enough. A suite filter appears only when one is set, which keeps an ordinary full pass short while making a single-suite investigation obvious next to the full matrix it was cut from. A scheduled run says "all coins" rather than naming one, because the schedule covers all three in a single run and naming a coin there would be a lie.
…wn block cadence The anchor-reward attestation barrier holds a block until the hub-mirror stream watermark is 120 seconds past that block's own timestamp. On a shared ledger that costs nothing, because blocks are ten minutes apart and the watermark is long past by the time one is processed. On regtest, blocks are stamped at about wall clock and the watermark tracks wall clock, so a freshly mined block can never be 120 seconds behind it. The condition cannot be satisfied, and every affected block waits out its full minute before proceeding anyway. Measured on the release matrix: the bitcoin leg parsed 367 blocks in six hours and was killed by the job budget, against 2013 blocks in under two hours on the build before the mirror was armed. A hundred and sixty deferrals, about two and three quarter hours spent waiting for something that could not arrive. The other two coins were green throughout, because the barrier is bitcoin-only, which is what made a venue constant sized for the wrong block cadence look like a defect on one chain. The indexer already had the seam for this and it is deliberately one-sided: the grace is honoured on regtest and ignored with a warning anywhere else, because a watermark grace is a consensus input and a per-node value forks settlement. This adds the matching passthrough, gated on regtest a second time so neither gate alone can carry a host variable onto a shared ledger, and sets it for the venue. Two seconds rather than zero, so the barrier still enforces the ordering it exists for and a genuine mirror-lag defect cannot ride through green.
…e state Review-round fixes. Three provisioning paths conflated "the inspection failed" with "the object is absent or empty", so a transient container, registry or database error let a destructive step proceed: a swallowed volume-wipe failure that then dropped the decoder and indexer databases, a registry error letting reset drop databases without stopping their services, and a transient database failure triggering destructive bootstrap during an update. A failed inspection is now an error rather than an emptiness verdict. Suite: 1988 passing, 0 failing.
The grace added earlier fixed a barrier that could never be satisfied. It revealed one that legitimately cannot be satisfied yet: on a venue that mines in seconds the hub mirror runs a minute or two behind the chain, so blocks defer for real and the barrier is right to hold them. What a fast venue cannot afford is the price of each hold. The attempt budget bounds one try, and on expiry the block is deferred and retried rather than committed uncertified, which the indexer states outright. So shortening it trades away no safety whatsoever; it only stops a correct decision from costing a minute every time it is taken. Measured at the default: a hundred and nineteen deferrals burned a hundred and nineteen minutes of a two hundred and eighty-nine minute bitcoin leg, two fifths of the wall clock, and left the indexer far enough behind that thirty end-to-end waits gave up on rows that had simply not landed. Ten seconds keeps the barrier honest and makes a deferral cost a tenth as much. Unlike the grace this is not a consensus input and carries no fork risk, but it is still passed through on regtest alone, because a shared ledger wants the long attempt: there a lagging mirror is a fault worth waiting on rather than a mismatch between block cadence and delivery rate.
…or two hours The e2e COINPay expiry case cannot wait out a two-hour obligation deadline, so it freezes the node clock past it and mines two blocks. Those blocks are then stamped two hours in the future, and the indexer's anchor-attest barrier holds a block until the hub's wall-clock watermark reaches its timestamp. The case avoids waiting two real hours and the indexer waits them instead. Measured on run 34015867460: every one of the 119 deferrals in the bitcoin leg named the same block, held for 2h08m50s out of a 289-minute run. Nothing was lagging. The watermark stayed within six seconds of wall clock the whole time and advanced at 0.9999 of real time, and the hub logged no late heartbeat, no backpressure close and no socket churn. The venue now sizes the window itself, at 300 seconds rather than 7200, which takes the clock jump from 2h10m to about six minutes. 300 and not less because the case still has to observe the obligation PENDING before expiring it, and a window shorter than that setup would expire it underneath the assertion. The window is a consensus input, so the indexer honours the override only on regtest and ignores it with a warning elsewhere; this passthrough is gated on regtest a second time, so neither gate alone can carry a host value onto a shared ledger. Also corrects the note on the attempt-budget knob above it, which claimed the mirror ran a minute or two behind this venue. It did not, and leaving that sentence in place would hand the next reader the wrong cause.
# Conflicts: # CHANGELOG.md # package-lock.json
A rebase onto develop brought an [Unreleased] section back above the release block, carrying one entry for host-env passthrough of the encoder and explorer rate-limit knobs. That code is in this release, so a changelog that files it under "unreleased" is wrong in the one place a reader checks to find out what shipped. Folding rather than deleting: the entry is real, it just belongs in the version that carries it. This is the step the recut leaves until the fold pass on purpose, because during a re-cut both sections are legitimately present and resolving it early would drop whichever side the rebase replayed second.
Operator ruling 2026-09-07: the point of this train is to exercise the response mirror and roll call on testnet, and a train that ships them dark there is not worth cutting. The mirror is armed at block 151324, the chain tip when the ruling was made, so it is active the moment a node updates rather than waiting on a future height. Roll call needed no change: it was already armed at 151200, which the chain passed some time ago. Mainnet stays unratified for both, so its behaviour is byte for byte unchanged. On testnet this changes state derived from existing bytes, so the changelogs now carry an Activation section saying so, and every hub and the indexers following it must update together rather than one at a time. The activation map is mirrored in five places and all five move together: both service copies, the documented canonical, the vendored copy the test helper reads, and the assertion that used testnet as its example of an unratified network, which it no longer is.
Generated from the actual tagged master merge commits, not by hand. All twelve components move to v0.15.0 on this train, the first full-set move since v0.12.0. Every pin was resolved from a tag that exists at origin, is reachable from that repo's master, and verifies against the platform release key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts xchain-node for the v0.15.0 release train. 29 commits from master.
Added
ENCODER_TRUST_PROXY,ENCODER_RATE_LIMIT_RPM, and five explorer per-route rate-limit knobs now pass through from the host env, so a container recreate no longer drops them.Fixed
validator initno longer mints a hub API key on a re-run, and the CLI sends the key it generated when it pushes config to the hub.HUB_RATE_LIMIT_EXEMPT_LOCALpasses through to the hub container.